package com.amkai.aes.webservice.binary.objecthandler;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.TimeZone;

import com.amkai.aes.aescommon.exceptions.AOException;
import com.amkai.aes.aescommon.factory.RADBeanFactory;
import com.amkai.aes.aescommon.service.binary.job.erp.ElectronicRemittancePostingInformation;
import com.amkai.aes.aescommon.service.binary.job.erp.ElectronicRemittancePostingJob;
import com.amkai.aes.aescommon.service.binary.job.erp.ElectronicRemittancePostingResult;
import com.amkai.aes.aescommon.service.diee.DIEECommonUtils;
import com.amkai.aes.aescommon.utilities.BeanFormatter;
import com.amkai.aes.generatedbeans.audittrail.AuditTrail;
import com.amkai.aes.generatedbeans.audittrail.AuditTrailKind;
import com.amkai.aes.generatedbeans.billinginterval.Batch;
import com.amkai.aes.generatedbeans.organization.Department;
import com.amkai.aes.generatedbeans.passport.Passport;
import com.amkai.aes.generatedbeans.remittanceposting.AuditTrailERPProperties;
import com.amkai.aes.generatedbeans.remittanceposting.ElectronicRemittanceFileFormat;
import com.amkai.aes.generatedbeans.remittanceposting.ElectronicRemittanceKind;
import com.amkai.aes.server.diee.DIEEServerUtils;
import com.amkai.aes.webservice.binary.JobObjectHandler;
import com.amkai.common.base.erp.ERPAdditionalInformation;
import com.amkai.common.base.erp.IERPData;
import com.amkai.common.base.erp.log.ERPLogManager;
import com.amkai.common.base.erp.log.generated.ERPLog;
import com.amkai.common.base.streams.StringOutputStream;
import com.amkai.common.extensions.hibernate.config.SiteManager;
import com.amkai.common.extensions.hibernate.config.manager.SessionManager;
import com.amkai.common.extensions.hibernate.tools.AuditTrailManager;
import com.amkai.extensions.server.services.IElectronicRemittancePostingManager;
import com.amkai.extensions.server.services.IElectronicRemittancePostingProvider;
import com.amkai.extensions.server.services.ServerServiceManager;

public class ElectronicRemittancePostingHandler implements JobObjectHandler<ElectronicRemittancePostingJob, Serializable, ElectronicRemittancePostingResult> {

	@Override
	public ElectronicRemittancePostingResult process(ElectronicRemittancePostingJob job, Serializable object) throws Exception {
		ElectronicRemittancePostingResult result = new ElectronicRemittancePostingResult();
		try {
			switch (job.getJobKind()) {
			case RUN_ERP:
				runERP(job, result);
				break;
			case LOAD_EOB_REPORT_IDENTIFICATIONS:
				loadEobReportIdentifications(job, result);
				break;
			case LOAD_LOG_REPORT_IDENTIFICATION:
				loadLogReportIdentification(job, result);
				break;
			case QUERY_ADDITIONAL_INFO:
				queryAdditionalInfo(job, result);
				break;
			case QUERY_POSTING_INFORMATIONS:
				queryPostingInformations(job, result);
				break;
			case REMOVE_FILES:
				removeFiles(job, result);
				break;
			case CHECK_MODULE_AVAILABLE:
				checkModuleAvailable(result, false);
				break;
			case QUERY_DEFAULT_ERP_KIND:
				queryDefaultERPKind(job, result);
				break;
			}
		} catch (Exception ex) {
			result.setException(ex);
		}
		return result;
	}
	
	private final IERPData getERPData(IElectronicRemittancePostingManager manager, ElectronicRemittanceFileFormat fileFormat, byte[] data, TimeZone timeZone) throws Exception {
		if (data!=null && data.length>0 && fileFormat!=null) {
			switch (fileFormat) {
			case GENERAL_FORMAT_835:
				return manager.parse835(data, timeZone);
			}
		}
		return null;
	}

	private final TimeZone getTimeZone(Department department) {
		if (department!=null && !DIEECommonUtils.isEmptyString(department.getTimeZone())) {
			return TimeZone.getTimeZone(department.getTimeZone());
		} else {
			return TimeZone.getDefault();
		}
	}
	
	private final void runERP(ElectronicRemittancePostingJob job, ElectronicRemittancePostingResult result) throws Exception {
		checkModuleAvailable(result, true);
		if (job.getKind()!=null) {
			if (job.getFileData()!=null && job.getFileData().length>0) {
				IElectronicRemittancePostingManager manager = ServerServiceManager.getInstance().getElectronicRemittancePostingManager();
				if (manager!=null) {
					SessionManager sm = SiteManager.Start();
					Batch batch = null;
					if (job.getBatchId()!=null)
						batch = sm.get(Batch.class, job.getBatchId());
					Passport passport = null;
					if (job.getPassportId()!=null)
						passport = sm.get(Passport.class, job.getPassportId());
					Department department = job.getModuleDepartment();
					if (department!=null) {
						department = sm.get(Department.class, department.getId());
					}
					TimeZone timeZone = getTimeZone(department);
					IERPData data = getERPData(manager, job.getKind().getFileFormat(), job.getFileData(), timeZone);
					data.setFileName(job.getFileName());
					IElectronicRemittancePostingProvider provider =
						manager.createElectronicRemittancePostingProvider(
								sm,
								data,
								job.isPostRequired(),
								passport,
								batch,
								job.getKind(),
								job.getAdditionalInfo(),
								department,
								DIEEServerUtils.getModuleAuditTrail(job));
					if (job.getHints()==null || job.getHints().isEmpty()) {
						provider.preProcess();
					} else {
						provider.setHints(job.getHints());
					}
					if (provider.isHintsRequired()) {
						result.setHints(provider.getHints());
					} else {
						sm.openTransaction();
						try {
							provider.process();
							if (job.isPostRequired())
								sm.commitTransaction();
							else
								sm.rollbackTransaction();
						} catch (Exception ex) {
							sm.rollbackTransaction();
							throw ex;
						}
						ERPLog log = provider.getLog();
						if (log!=null) {
							ERPLogManager logManager = new ERPLogManager();
							StringOutputStream stream = new StringOutputStream();
							logManager.writeToOutputStream(stream, log);
							result.setLogContent(stream.toString());
						}
					}
				}
			} else {
				throw new AOException("File data is empty");
			}
		} else {
			throw new IllegalStateException("Illegal job: kind is empty");
		}
	}
	
	private final void loadEobReportIdentifications(ElectronicRemittancePostingJob job, ElectronicRemittancePostingResult result) throws Exception {
		checkModuleAvailable(result, true);
		IElectronicRemittancePostingManager manager = ServerServiceManager.getInstance().getElectronicRemittancePostingManager();
		if (manager!=null) {
			SessionManager sm = SiteManager.Start();
			Department department = job.getModuleDepartment();
			if (department!=null) {
				department = sm.get(Department.class, department.getId());
			}
			result.setEobReportIdentifications(manager.getEOBReportIdentifications(sm, department));
		}
	}
	
	private final void loadLogReportIdentification(ElectronicRemittancePostingJob job, ElectronicRemittancePostingResult result) throws Exception {
		checkModuleAvailable(result, true);
		IElectronicRemittancePostingManager manager = ServerServiceManager.getInstance().getElectronicRemittancePostingManager();
		if (manager!=null) {
			SessionManager sm = SiteManager.Start();
			Department department = job.getModuleDepartment();
			if (department!=null) {
				department = sm.get(Department.class, department.getId());
			}
			result.setLogReportIdentification(manager.getLogReportIdentification(sm, department));
		}
	}
	
	private final void queryAdditionalInfo(ElectronicRemittancePostingJob job, ElectronicRemittancePostingResult result) throws Exception {
		checkModuleAvailable(result, true);
		IElectronicRemittancePostingManager manager = ServerServiceManager.getInstance().getElectronicRemittancePostingManager();
		if (manager!=null) {
			TimeZone timeZone = getTimeZone(job.getModuleDepartment());
			IERPData data = getERPData(manager, job.getKind().getFileFormat(), job.getFileData(), timeZone);
			ERPAdditionalInformation info = manager.getAdditionalInfo(data);
			result.setAdditionalInfo(info);
		}
	}

	private final void queryPostingInformations(ElectronicRemittancePostingJob job, ElectronicRemittancePostingResult result) throws Exception {
		checkModuleAvailable(result, true);
		HashMap<String, ElectronicRemittancePostingInformation> informations = new HashMap<String, ElectronicRemittancePostingInformation>();
		SessionManager sm = SiteManager.Start();
		// TODO: handle removed files
		List<Object[]> list = sm.execute(
				"select p.fileName, max(a.created), h.lastName, h.firstName, h.middleInitial, h.title " +
				"from AuditTrail as a " +
				"inner join a.auditTrailERPProperties as p " +
				"left join a.passport as u " +
				"left join u.person as h " +
				"where p.kind.id = :kindId and p.removed = false and not exists (" +
				"select pp.id from AuditTrailERPProperties as pp " +
				"where pp.kind.id = p.kind.id and pp.fileName = p.fileName and pp.removed = true" +
				")" +
				"group by p.fileName, h.lastName, h.firstName, h.middleInitial, h.title"
			).setLong("kindId", job.getKind().getId()).list();
		if (list!=null) {
			for (Object[] objs : list) {
				String fileName = (String)objs[0];
				ElectronicRemittancePostingInformation info = informations.get(fileName);
				if (info==null) {
					info = new ElectronicRemittancePostingInformation();
					info.setFileName(fileName);
					informations.put(fileName, info);
				}
				Date created = (Date)objs[1];
				if (created!=null) {
					if (info.getPostingDate()==null || created.after(info.getPostingDate())) {
						info.setPostingDate(created);
						info.setPassportName(BeanFormatter.getPersonName((String)objs[2], (String)objs[3], (String)objs[4], (String)objs[5]));
					}
				}
			}
			result.setPostingInformations(informations.values().toArray(new ElectronicRemittancePostingInformation[informations.size()]));
			informations.clear();
		}
	}

	private final void removeFiles(ElectronicRemittancePostingJob job, ElectronicRemittancePostingResult result) throws Exception {
		checkModuleAvailable(result, true);
		IElectronicRemittancePostingManager manager = ServerServiceManager.getInstance().getElectronicRemittancePostingManager();
		if (manager!=null) {
			if (job.getKind()!=null && job.getFileNames()!=null && job.getFileNames().length>0) {
				AuditTrail moduleAuditTrail = DIEEServerUtils.getModuleAuditTrail(job);
				if (moduleAuditTrail==null)
					throw new AOException("Audit trail parent not found - can't create file history.");
				SessionManager sm = SiteManager.Start();
				sm.openTransaction();
				try {
					Department department = job.getModuleDepartment();
					if (department!=null)
						department = sm.get(Department.class, department.getId());
					Passport passport = null;
					if (job.getPassportId()!=null)
						passport = sm.get(Passport.class, job.getPassportId());
					ElectronicRemittanceKind kind = sm.get(ElectronicRemittanceKind.class, job.getKind().getId());
					for (String fileName : job.getFileNames()) {
						AuditTrail auditTrail = AuditTrailManager.createAuditTrailAndSetCommonProperties(AuditTrailKind.MODULE, moduleAuditTrail);
						auditTrail.setPassport(passport);
						auditTrail.setBaseOrganization(department);
						auditTrail.setDescription("ERA file is removed: " + fileName + (kind==null ? "" : " (" + kind.getName() + ")"));
						AuditTrailERPProperties props = RADBeanFactory.getInstance().createBean(AuditTrailERPProperties.class);
						props.getAuditTrail().add(auditTrail);
						props.setKind(kind);
						props.setFileName(fileName);
						props.setRemoved(true);
						auditTrail.setAuditTrailERPProperties(props);
						sm.saveRAD(props);
						sm.saveRAD(auditTrail);
					}
					sm.commitTransaction();
				} catch (Exception ex) {
					sm.rollbackTransaction();
					throw ex;
				}
			}
		}
	}

	private final void checkModuleAvailable(ElectronicRemittancePostingResult result, boolean throwException) throws Exception {
		IElectronicRemittancePostingManager manager = ServerServiceManager.getInstance().getElectronicRemittancePostingManager();
		// AES-17445
		result.setModuleAvailable(manager!=null && manager.isInitialized());
		if (throwException && !result.isModuleAvailable()) {
			throw new AOException("Electronic remittance posting manager is not started yet");
		}
	}
	
	private final void queryDefaultERPKind(ElectronicRemittancePostingJob job, ElectronicRemittancePostingResult result) throws Exception {
		checkModuleAvailable(result, true);
		SessionManager sm = SiteManager.Start();
		Department department = sm.get(Department.class, job.getModuleDepartment().getId());
		List<ElectronicRemittanceKind> list = sm.execute(
				"select k from ElectronicRemittanceKind as k " +
				"where k.active = true and k.baseOrganization in (" + department.getId() + ", " + department.getParent().getId() + ")"
			).list();
		if (list!=null && list.size()==1)
			result.setKind(list.get(0));
	}
}
